﻿// SwaggerSettings.cs
// Copyright (c) 2022 chenchao
// Email:cc@on8.top

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.Extensions.DependencyModel;
using System.Linq;
using System.Runtime.Loader;
using Microsoft.AspNetCore.Mvc;

namespace OnWork.Tools.Swagger
{
    /// <summary>
    /// swaggerSettings 配置对象
    /// </summary>
    public static class SwaggerSettings
    {
        /// <summary>
        /// 设置对象
        /// </summary>
        private static SwaggerSettingsOptions settingsOptions = new SwaggerSettingsOptions();

        /// <summary>
        /// 获取配置对象
        /// </summary>
        public static SwaggerSettingsOptions SettingsOptions { get => settingsOptions; }

        /// <summary>
        /// 初始化配置
        /// </summary>
        /// <param name="Options"></param>
        public static void Init(SwaggerSettingsOptions Options)
        {
            settingsOptions = Options;
            InitGroups(); //初始化分组配置
        }

        /// <summary>
        /// 初始化分组配置
        /// </summary>
        private static void InitGroups()
        {

            //获取分组名称
            HashSet<string> groupNames = new HashSet<string>();
            groupNames.Add(settingsOptions.DefaultGroupName); //添加默认的分组名称

            #region 反射获取 注解的Group配置内容 
            //获取所有控制器和方法的Group配置
            //获取依赖对象对象名称
            var dependencyContext = DependencyContext.Default;
            var scanAssemblieNames = dependencyContext.RuntimeLibraries
                .Where(u => u.Type == "project")
               .Select(u => u.Name)
               .ToList();

            //加载反射对象
            foreach (var assemblyName in scanAssemblieNames) {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));
                foreach (var type in assembly.GetTypes()) {
                    //公共类型  
                    if (type.IsPublic == true) //公共类型
                    {
                        //如果类注解包含group属性则添加到
                        var apiattr = type.GetCustomAttribute<ApiExplorerSettingsOnWorkAttribute>(true);
                        if (apiattr != null) {
                            //判断多分组的情况
                            var group = apiattr.Groups;
                            for (int i = 0; i < group.Length; i++) {
                                groupNames.Add(group[i]);
                            }
                        }

                        //如果方法上注解包含group属性则添加到
                        var methods = type.GetMethods();
                        foreach (var method in methods) {
                            var mattrs = method.GetCustomAttribute<ApiExplorerSettingsOnWorkAttribute>(true);
                            if (mattrs != null) {
                                //判断多分组的情况
                                var group = mattrs.Groups;
                                for (int i = 0; i < group.Length; i++) {
                                    groupNames.Add(group[i]);
                                }
                            }
                        }
                    }
                }
            }
            #endregion

            //配置的信息
            var settingOpenInfos = settingsOptions.GroupOpenApiInfos;
            //将文件配置的和注解配置的取并集
            List<OpenApiSettings> openApis = new List<OpenApiSettings>();
            foreach (var groupName in groupNames) {
                OpenApiSettings openApiSettings = new OpenApiSettings()
                {
                    Group = groupName,
                    Title = groupName,
                };
                if (settingOpenInfos != null) {
                    //查询是否有配置
                    var fisrt = settingOpenInfos.Where(x => x.Group == groupName).FirstOrDefault();
                    if (fisrt != null) { //有配置则取配置的值
                        openApiSettings = fisrt;
                    }
                }
                openApis.Add(openApiSettings);
            }

            //增加排序和筛选
            settingsOptions.GroupOpenApiInfos = openApis.Where(x => x.Visible == true).OrderBy(x => x.Order).ThenBy(x => x.Group).ToList();
        }

        /// <summary>
        /// 配置 SwaggerGen
        /// </summary>
        /// <param name="swaggerGenOptions"></param>
        /// <param name="swaggerGenOptionsAction">委托方法</param>
        internal static void SwaggerGen(SwaggerGenOptions swaggerGenOptions, Action<SwaggerGenOptions> swaggerGenOptionsAction)
        {
            //加载分组文档配置
            LoadSwaggerDoc(swaggerGenOptions);
            //加载分组控制器配置
            LoadGroupControll(swaggerGenOptions);
            //加载注释描述文件
            LoadXmlComments(swaggerGenOptions);
            //自定义返回类型
            LoadCustomSchemaIds(swaggerGenOptions);
            //配置读取自定义分组
            LoadTagActionsBy(swaggerGenOptions);

            //如果不为空则覆盖配置
            if (swaggerGenOptionsAction != null) {
                swaggerGenOptionsAction.Invoke(swaggerGenOptions);
            }
        }

        /// <summary>
        /// 配置自定义分组
        /// </summary>
        /// <param name="swaggerGenOptions"></param>
        private static void LoadTagActionsBy(SwaggerGenOptions swaggerGenOptions)
        {
            swaggerGenOptions.TagActionsBy(api => {
                //获取方法 如何包含tag属性则返回tag
                if (api.TryGetMethodInfo(out var method)) {
                    var apidesc = method.GetCustomAttribute<ApiExplorerSettingsOnWorkAttribute>();
                    if (apidesc != null && !string.IsNullOrEmpty(apidesc.Tag)) {
                        return new[] { apidesc.Tag.Trim() };
                    }
                }
                // 获取控制器描述器
                var controllerActionDescriptor = api.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;
                var type = controllerActionDescriptor.ControllerTypeInfo;
                // 如果动作方法没有定义 [ApiDescriptionSettings] 特性，则返回所在控制器名
                if (!type.IsDefined(typeof(ApiExplorerSettingsOnWorkAttribute), true)) return new[] { controllerActionDescriptor.ControllerName };
                // 读取标签
                var apiDescriptionSettings = type.GetCustomAttribute<ApiExplorerSettingsOnWorkAttribute>(true);
                return new[] { string.IsNullOrWhiteSpace(apiDescriptionSettings.Tag) ? controllerActionDescriptor.ControllerName : apiDescriptionSettings.Tag };
            });
        }


        /// <summary>
        /// 自定义返回类型 防止类名重复的报错问题
        /// </summary>
        /// <param name="swaggerGenOptions"></param>
        private static void LoadCustomSchemaIds(SwaggerGenOptions swaggerGenOptions)
        {
            swaggerGenOptions.CustomSchemaIds(CustomSchemaIdSelector);
        }

        /// <summary>
        /// 自定义自定义SchemaId
        /// </summary>
        /// <param name="modelType"></param>
        /// <returns></returns>
        private static string CustomSchemaIdSelector(Type modelType)
        {
            //如果此对象不是构造泛型类型, 则返回 FullName
            if (!modelType.IsConstructedGenericType) return modelType.FullName.Replace("[]", "Array");

            //泛型类型的 则遍历
            var prefix = modelType.GetGenericArguments()
                .Select(genericArg => CustomSchemaIdSelector(genericArg))
                .Aggregate((previous, current) => previous + current);
            return prefix + modelType.FullName.Split('`').First();
        }

        /// <summary>
        /// 加载分组控制器 配置文件显示
        /// </summary>
        private static void LoadGroupControll(SwaggerGenOptions swaggerGenOptions)
        {
            //根据分组名称 groupName 判断是否返回结果
            swaggerGenOptions.DocInclusionPredicate((groupName, apiDescription) => {
                //获取反射方法信息
                if (!apiDescription.TryGetMethodInfo(out MethodInfo method)) {
                    return false;
                }

                /*使用ApiDescriptionSettingsAttribute里面的Groups进行特性标识
                  * DeclaringType只能获取controller上的特性
                  * 我们这里是想以action的特性为主
                  * */
                //这里获取action的特性
                var actionVersion1 = method.GetCustomAttributes(true).OfType<ApiExplorerSettingsOnWorkAttribute>().Select(m => m.Groups);
                if (actionVersion1.Any()) {
                    return actionVersion1.Any(v => v.Contains(groupName));
                }
                //这里获取controll的特性
                var version1 = method.DeclaringType.GetCustomAttributes(true).OfType<ApiExplorerSettingsOnWorkAttribute>().Select(m => m.Groups);
                if (version1.Any()) {
                    return version1.Any(v => v.Contains(groupName));
                }

                /*使用ApiExplorerSettingsAttribute里面的GroupName进行特性标识
                   * DeclaringType只能获取controller上的特性
                   * 我们这里是想以action的特性为主
                   * */

                //这里获取action的特性
                var actionVersion = method.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
                if (actionVersion.Any()) {
                    return actionVersion.Any(v => v == groupName);
                }
                //这里获取controll的特性
                var version = method.DeclaringType.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
                if (version.Any()) {
                    return version.Any(v => v == groupName);
                }

                //如果都没有标记的话则进入默认
                if (settingsOptions.DefaultGroupName == groupName) {
                    return true;
                }
                else {
                    return false;
                }
            });
        }

        /// <summary>
        /// 加载分组文档配置 InitGroups会初始化分组信息
        /// </summary>
        /// <param name="swaggerGenOptions"></param>
        private static void LoadSwaggerDoc(SwaggerGenOptions swaggerGenOptions)
        {
            var DocumentGroups = settingsOptions.GroupOpenApiInfos;

            foreach (var group in DocumentGroups) {
                swaggerGenOptions.SwaggerDoc(group.Group, group);
            }
        }


        /// <summary>
        /// 加载注释描述文件
        /// </summary>
        /// <param name="swaggerGenOptions">Swagger 生成器配置</param>
        private static void LoadXmlComments(SwaggerGenOptions swaggerGenOptions)
        {
            var xmlComments = settingsOptions.XmlComments;

            //获取依赖对象对象名称
            var dependencyContext = DependencyContext.Default;
            var scanAssemblies = dependencyContext.RuntimeLibraries
                .Where(u => u.Type == "project")
               .Select(u => u.Name)
               .ToList();

            if (xmlComments != null && xmlComments.Count > 0) {
                scanAssemblies.AddRange(xmlComments);
            }
            xmlComments = scanAssemblies.Distinct().ToList();

            //遍历xml文件添加
            foreach (var xmlComment in xmlComments) {
                var assemblyXmlName = xmlComment.EndsWith(".xml") ? xmlComment : $"{xmlComment}.xml";
                var assemblyXmlPath = Path.Combine(AppContext.BaseDirectory, assemblyXmlName);
                if (File.Exists(assemblyXmlPath)) {
                    swaggerGenOptions.IncludeXmlComments(assemblyXmlPath, true);
                }
            }
        }

        /// <summary>
        /// 配置 UseSwagger
        /// </summary>
        /// <param name="swaggerOptions"></param>
        internal static void UseSwagger(SwaggerOptions swaggerOptions, Action<SwaggerOptions> swaggerOptionsAction)
        {
            //增加自定义配置
            if (swaggerOptionsAction != null) {
                swaggerOptionsAction.Invoke(swaggerOptions);
            }
        }

        /// <summary>
        /// 配置 UseSwaggerUI
        /// </summary>
        /// <param name="swaggerUIOptions"></param>
        /// <param name="routePrefix"></param>
        internal static void UseSwaggerUI(SwaggerUIOptions swaggerUIOptions, Action<SwaggerUIOptions> swaggerUIOptionsAction)
        {
            // 配置文档标题
            swaggerUIOptions.DocumentTitle = settingsOptions.DocumentTitle;

            // 配置UI地址（处理二级虚拟目录）
            swaggerUIOptions.RoutePrefix = settingsOptions.RoutePrefix ?? "swagger";

            // 文档展开设置
            if (settingsOptions.DocExpansionState != null) swaggerUIOptions.DocExpansion(settingsOptions.DocExpansionState.Value);

            LoadSwaggerEndpoint(swaggerUIOptions);

            //增加自定义配置
            if (swaggerUIOptionsAction != null) {
                swaggerUIOptionsAction.Invoke(swaggerUIOptions);
            }
        }

        /// <summary>
        /// 加载跟节点
        /// </summary>
        /// <param name="swaggerUIOptions"></param>
        private static void LoadSwaggerEndpoint(SwaggerUIOptions swaggerUIOptions)
        {
            //swaggerUIOptions.SwaggerEndpoint("测试/swagger.json", "My API V1");
            var DocumentGroups = settingsOptions.GroupOpenApiInfos;
            foreach (var group in DocumentGroups) {
                //var groupOpenApiInfo = (group) as OpenApiInfo;
                swaggerUIOptions.SwaggerEndpoint($"/swagger/{group.Group}/swagger.json", group.Title);
            }
        }
    }
}
